﻿/*
This project and all of its code is/are Copyright (C) 2017  Nils Hunter Alving

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/
using System;
using System.Collections.Generic;
using System.Linq;

namespace DynamicTypeBuilder.ConstrainedType
{
    /// <summary>
    /// The base class for all ConstrainedTypes
    /// </summary>
    /// <typeparam name="T"></typeparam>
    public abstract class ConstrainedTypeBase<T>
    {
        /// <summary>
        /// The list of predefined values
        /// </summary>
        public readonly Dictionary<T, int> Values;

        private T _value;
        /// <summary>
        /// The current value of the ConstrainedType instance
        /// </summary>
        public T Value
        {
            get
            {
                return _value;
            }
            set
            {
                if (!Values.ContainsKey(value))
                {
                    throw new ArgumentException($"'{value}' is not a valid value for type {GetType().Name}");
                }
                _value = value;
            }
        }

        /// <summary>
        /// The numeric value associated with the current Value property
        /// </summary>
        public int Index
        {
            get
            {
                return Values[_value];
            }
        }

        /// <summary>
        /// The base class constructor
        /// </summary>
        /// <param name="values">A list of the pre-defined values for this type</param>
        public ConstrainedTypeBase(List<T> values)
        {
            Values = new Dictionary<T, int>();
            InitializeValues(values);
        }
        /// <summary>
        /// A constructor that provides for the definition of custom indexes for the predefined values
        /// </summary>
        /// <param name="values">A dictionary of constrained values and their desired indexes</param>
        public ConstrainedTypeBase(Dictionary<T, int?> values)
        {
            Values = new Dictionary<T, int>();
            InitializeValues(values);
        }

        private void InitializeValues(List<T> values)
        {
            var index = 0;

            values.ForEach(v => Values.Add(v, index++));
        }
        private void InitializeValues(Dictionary<T, int?> values)
        {
            var index = 0;
            var difference = 1;

            values.ToList().ForEach(v =>
            {
                if (v.Value.HasValue)
                {
                    if (index > 0 && v.Value <= index)
                    {
                        throw new ArgumentException($"Index value for key {v.Key} of {v.Value} is not greater than previous value of {index}");
                    }

                    difference = Math.Max(1, v.Value.Value - index);
                    index = v.Value.Value;
                }
                else
                {
                    index += difference;
                }
                Values.Add(v.Key, index);
            });
        }
        /// <summary>
        /// Implicit operator that allows the direct conversion of an instance of the type to the type of the current Value
        /// </summary>
        /// <param name="constrainedType">The instance to be converted</param>
        public static implicit operator T(ConstrainedTypeBase<T> constrainedType)
        {
            return constrainedType.Value;
        }
        /// <summary>
        /// Converts the Value property to a string
        /// </summary>
        /// <returns>The ToString() value of the Value property</returns>
        public override string ToString()
        {
            return Value.ToString();
        }
    }
}
